package simtester.agent

import static org.junit.Assert.*

import groovy.mock.interceptor.MockFor
import groovy.mock.interceptor.StubFor
import groovyx.net.http.RESTClient
import java.util.logging.Logger
import java.util.regex.Matcher
import java.util.regex.Pattern
import org.junit.After
import org.junit.AfterClass
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test

class AgentTests {
    static configFile
    static bareScript
    static failingScript
    static loadScript
    static testScript
    static finishFile = new File('finished')
    static serverurl = 'http://testserver:8080/server/'
    static testrunid = '111111111111111111111111'
    
    @BeforeClass
    static void setUpClass() {
        System.properties.with { prop ->
            prop['org.apache.commons.logging.Log']='org.apache.commons.logging.impl.SimpleLog'
            prop['org.apache.commons.logging.simplelog.log.com.gargoylesoftware.htmlunit']='FATAL'
        }
        bareScript = new File('UnitTestScript.groovy')
        def scriptText = '''\
package simtester.agent
import geb.*
import groovyx.net.http.ContentType
import java.util.logging.*
class AgentScript {
  static runtest(args) {
  }
}'''
        bareScript.write(scriptText)
        
        failingScript = new File('FailingScript.groovy')
        scriptText = '''\
package simtester.agent
import geb.*
import groovyx.net.http.ContentType
import java.util.logging.*
class AgentScript {
  static runtest(args) {
    assert 1 == 2
  }
}'''
        failingScript.write(scriptText)
        
        loadScript = new File('LoadScript.groovy')
        scriptText = '''\
package simtester.agent
import geb.*
import groovyx.net.http.ContentType
class AgentScript {
  static runtest(args) {
    def log = args.logger
    def sutAddr = 'http://www.google.com/'
    def sut = new RESTCli(args, sutAddr, ContentType.HTML)
    sut.fakeget(path: '')
    sleep 500
  }
}'''
        loadScript.write(scriptText)

        testScript = new File('TestScript.groovy')
        scriptText = '''\
package simtester.agent
import geb.*
import groovyx.net.http.ContentType
class AgentScript {
  static runtest(args) {
    def log = args.logger
    def sutAddr = 'http://www.google.com/'
    def sut = new RESTCli(args, sutAddr, ContentType.HTML)
    10.times { sut.fakeget(path: '') }
  }
}'''
        loadScript.write(scriptText)
    }
    
    @AfterClass
    static void tearDownClass() {
        bareScript.delete()
        failingScript.delete()
        loadScript.delete()
    }

    @Before
    public void setUp() {
        configFile = new File('TestConf.groovy')
        configFile << "serverurl = '$serverurl'\n"
        configFile << "testrunid = '$testrunid'\n"
    }

    @After
    public void tearDown() {
        if (finishFile.exists()) {
            finishFile.delete()
        }
        configFile.delete()
        new File('.').eachFile { file ->
            if (file.isFile() && file.name =~ /^agent\.log\.?\d*$/) {  //$
                println file.text
                file.delete()
            }
        }
    }
    
   @Test
    void printConnectionError() {
        def stubLogger = new StubFor(Logger)
        
        def testlog = ''
        stubLogger.ignore('getLogger')
        stubLogger.ignore('addHandler')
        stubLogger.ignore('getHandlers')
        stubLogger.ignore.info { msg ->
            testlog += "$msg\n"
        }
        stubLogger.ignore.severe { msg ->
            testlog += "$msg\n"
        }
        
        stubLogger.use {
            // Server does not run during test, so we can be sure that there will always be a connection error
            def agent = new Agent(configFile)
            agent.start(bareScript)
        }
        
        assert testlog =~ /Unknown host: testserver/
    }
    
    @Test
    void runSuccessfulTest() {
        def mockRestClient = new MockFor(RESTCli)
        def putRequest
        def postRequest = [body: [results: '']]
        mockRestClient.ignore('fakeget')
        mockRestClient.demand.put { map ->
            putRequest = map
            return [data:[msg: 'ok']]
        }
        mockRestClient.ignore.postNoLog { map ->
            postRequest.path = map.path
            postRequest.body.results += map.body.results
            return [data:[msg: 'ok']]
        }

        mockRestClient.use {
            def agent = new Agent(configFile)
            agent.start()
        }
    
        // Check that the correct put request was sent
        assert putRequest.path == "testRun/update/$testrunid"
        assert putRequest.body.status == 'completed'
        
        def logtext = new File('agent.log').text
        assert logtext.contains('----- Start Test Run 1:1 -----')
        assert logtext.contains('user:1 iter:1|Title: Google')
        assert logtext.findAll(/INFO: user:1 iter:1 resp:\d+ type:GET\|http:\/\/www\.google\.com/).size() == 10 // AgentScript has 10 GETs
        assert logtext.contains('INFO: user:1 iter:1|SYS:testrun status:success')
        assert logtext.contains('----- End Test Run 1:1 -----')
        assert logtext.contains('Test Completed')
        assert logtext.contains('Sending request to complete test execution')
        assert logtext.contains("Success: status of test execution is now 'completed'")

        // Check that the correct post request was sent
        assert postRequest.path == "testRun/sendresults/$testrunid"
        def results = postRequest.body.results
        assert results.contains('----- Start Test Run 1:1 -----')
        assert results.contains('user:1 iter:1|Title: Google')
        assert results.findAll(/INFO: user:1 iter:1 resp:\d+ type:GET\|http:\/\/www\.google\.com/).size() == 10
        assert results.contains('INFO: user:1 iter:1|SYS:testrun status:success')
        assert results.contains('----- End Test Run 1:1 -----')
        assert results.contains('Test Completed')
        
        // At end of test, create file to indicate that it's finished
        assert new File('finished').exists()
    }
    
    @Test
    void runLoadTest() {
        def mockRestClient = new MockFor(RESTCli)
        def putRequest
        def postRequest = [body: [results: '']]
        mockRestClient.ignore('fakeget')
        mockRestClient.ignore.put { map ->
            putRequest = map
            return [data:[msg: 'ok']]
        }
        mockRestClient.ignore.postNoLog { map ->
            postRequest.path = map.path
            postRequest.body.results += map.body.results
            return [data:[msg: 'ok']]
        }
        
        def tcId = '12345'
        configFile << "testcaseid = '$tcId'\n"
        configFile << "duration = 4000\n"
        configFile << "totalduration = 5000\n"
        configFile << "firstuser = 2\n"
        configFile << "peruser = 5\n"
        configFile << "per = 2000\n"
        configFile << "delay = 1000\n"

        mockRestClient.use {
            def agent = new Agent(configFile)
            agent.start(loadScript)
        }
        
        // Check that the correct put request was sent
        //assert putRequest.path == "testRun/update/$testrunid"
        //assert putRequest.body.status == 'completed'
        
        // Check that the correct post request was sent
        //assert postRequest.path == "testRun/sendresults/$testrunid"
        //def results = postRequest.body.results
        def logtext = new File('agent.log').text
        assert logtext.findAll(/----- Start Test Run \d+:1:$tcId -----/).size() == 10
        assert logtext.findAll(/INFO: tc:$tcId user:\d+ iter:1 resp:\d+ type:GET\|http:\/\/www\.google\.com/).size() > 1
        assert logtext.findAll(/INFO: tc:$tcId user:\d+ iter:1|SYS:testrun status:success/).size() > 1
        assert logtext.findAll(/----- End Test Run \d+:1:$tcId -----/).size() == 10
        assert logtext.count('Test Completed') == 1
        
        // At end of test, create file to indicate that it's finished
        assert new File('finished').exists()
    }
    
    @Test
    void runLoadTest2() {
        def mockRestClient = new MockFor(RESTCli)
        def putRequest
        def postRequest = [body: [results: '']]
        mockRestClient.ignore('fakeget')
        mockRestClient.ignore.put { map ->
            putRequest = map
            return [data:[msg: 'ok']]
        }
        mockRestClient.ignore.postNoLog { map ->
            postRequest.path = map.path
            postRequest.body.results += map.body.results
            return [data:[msg: 'ok']]
        }
        
        def tcId = '12345'
        configFile << "testcaseid = '$tcId'\n"
        configFile << "duration = 400\n"
        configFile << "totalduration = 500\n"
        configFile << "firstuser = 1\n"
        configFile << "peruser = 1\n"
        configFile << "per = 300\n"
        configFile << "delay = 0\n"

        mockRestClient.use {
            def agent = new Agent(configFile)
            agent.start(loadScript)
        }
        
        // Check that the correct put request was sent
        //assert putRequest.path == "testRun/update/$testrunid"
        //assert putRequest.body.status == 'completed'
        
        // Check that the correct post request was sent
        //assert postRequest.path == "testRun/sendresults/$testrunid"
        //def results = postRequest.body.results
        def logtext = new File('agent.log').text
        assert logtext.contains("----- Start Test Run 1:1:$tcId -----")
        assert logtext.contains("----- Start Test Run 2:1:$tcId -----")
        assert logtext.contains("----- End Test Run 1:1:$tcId -----")
        assert logtext.contains("----- End Test Run 2:1:$tcId -----")
        assert logtext.count('Test Completed') == 1
        
        // At end of test, create file to indicate that it's finished
        assert new File('finished').exists()
    }
    
    @Test
    void runFailingTest() {
        def mockRestClient = new MockFor(RESTCli)
        def putRequest
        def postRequest = [body: [results: '']]
        mockRestClient.ignore('fakeget')
        mockRestClient.demand.put { map ->
            putRequest = map
            return [data:[msg: 'ok']]
        }
        mockRestClient.ignore.postNoLog { map ->
            postRequest.path = map.path
            postRequest.body.results += map.body.results
            return [data:[msg: 'ok']]
        }

        mockRestClient.use {
            def agent = new Agent(configFile)
            agent.start(failingScript)
        }
        
        def results = postRequest.body.results
        assert results.contains('Assertion Error:\\nassert 1 == 2\\n         |\\n         false')
    }
    
    @Test
    void repeatTestMultipleTimes() {
        def mockRestClient = new MockFor(RESTCli)
        def postRequest = [body: [results: '']]
        mockRestClient.ignore('fakeget')
        mockRestClient.demand.put { map ->
            return [data:[msg: 'ok']]
        }
        mockRestClient.ignore.postNoLog { map ->
            postRequest.path = map.path
            postRequest.body.results += map.body.results
            return [data:[msg: 'ok']]
        }

        def numRepeats = 3
        configFile << "repeats = $numRepeats\n"
        mockRestClient.use {
            new Agent(configFile).start(testScript)
        }
        
        // Check that the post request contains correct number of GETs
        assert postRequest.path == "testRun/sendresults/$testrunid"
        def results = postRequest.body.results
        
        // There are 10 GET requests in AgentScript
        assert (10 * numRepeats) == results.findAll(/INFO: user:\d+ iter:\d+ resp:\d+ type:GET\|http:\/\/www\.google\.com/).size()
        
        // Test iteration status
        numRepeats.times {
            def count = it + 1
            assert results.contains("----- Start Test Run 1:$count -----")
            assert results.contains("INFO: user:1 iter:$count|SYS:testrun status:success")
            assert results.contains("----- End Test Run 1:$count")
        } 
    }
    
    @Test
    void repeatTestWithMultipleUsers() {
        def mockRestClient = new MockFor(RESTCli)
        def postRequest = [body: [results: '']]
        mockRestClient.ignore('fakeget')
        mockRestClient.demand.put { map ->
            return [data:[msg: 'ok']]
        }
        mockRestClient.ignore.postNoLog { map ->
            postRequest.path = map.path
            postRequest.body.results += map.body.results
            return [data:[msg: 'ok']]
        }

        def numRepeats = 1
        def firstuser = 3
        def numUsers = 2
        configFile << "repeats = $numRepeats\n"
        configFile << "firstuser = $firstuser\n"
        configFile << "users = $numUsers\n"
        mockRestClient.use {
            new Agent(configFile).start(testScript)
        }
        
        // Check that the post request contains correct number of GETs
        assert postRequest.path == "testRun/sendresults/$testrunid"
        def results = postRequest.body.results
        
        // There are 10 GET requests in AgentScript
        assert (10 * numRepeats * numUsers) == results.findAll(/INFO: user:\d+ iter:\d+ resp:\d+ type:GET\|http:\/\/www\.google\.com/).size()
        
        // Test iteration status
        def lastuser = firstuser + numUsers - 1
        for (user in firstuser..lastuser) {
            numRepeats.times {
                def count = it + 1
                assert results.contains("----- Start Test Run $user:$count -----")
                assert results.contains("INFO: user:$user iter:$count|SYS:testrun status:success")
                assert results.contains("----- End Test Run $user:$count")
            }
        }
    }
    
    @Test
    void runTestWithDuration() {
        def mockRestClient = new MockFor(RESTCli)
        def postRequest = [body: [results: '']]
        mockRestClient.ignore('fakeget')
        mockRestClient.demand.put { map ->
            return [data:[msg: 'ok']]
        }
        mockRestClient.ignore.postNoLog { map ->
            postRequest.path = map.path
            postRequest.body.results += map.body.results
            return [data:[msg: 'ok']]
        }
        
        def duration = 7000  // 7 seconds
        configFile << "duration = $duration\n"
        mockRestClient.use {
            new Agent(configFile).start(testScript)
        }
        
        // Results should have more than one run
        def results = postRequest.body.results
        assert results.findAll(/----- Start Test Run 1:\d+/).size() > 1
        assert results.findAll(/INFO: user:1 iter:\d+\|SYS:testrun status:success/).size() > 1
        assert results.findAll(/----- End Test Run 1:\d+/).size() > 1
    }
    
    @Test
    void runTestWithTestcaseId() {
        def mockRestClient = new MockFor(RESTCli)
        def postRequest = [body: [results: '']]
        mockRestClient.ignore('fakeget')
        mockRestClient.demand.put { map ->
            return [data:[msg: 'ok']]
        }
        mockRestClient.ignore.postNoLog { map ->
            postRequest.path = map.path
            postRequest.body.results += map.body.results
            return [data:[msg: 'ok']]
        }

        def numRepeats = 1
        def firstuser = 3
        def numUsers = 2
        def testcaseId = '222222222222222222222222'
        configFile << "repeats = $numRepeats\n"
        configFile << "firstuser = $firstuser\n"
        configFile << "users = $numUsers\n"
        configFile << "testcaseid = '$testcaseId'\n"
        mockRestClient.use {
            new Agent(configFile).start(testScript)
        }
        
        // Check that the post request contains correct number of GETs
        assert postRequest.path == "testRun/sendresults/$testrunid"
        def results = postRequest.body.results
        
        // There are 10 GET requests in AgentScript
        assert (10 * numRepeats * numUsers) == results.findAll(/INFO: tc:$testcaseId user:\d+ iter:\d+ resp:\d+ type:GET\|http:\/\/www\.google\.com/).size()
        
        // Test iteration status
        def lastuser = firstuser + numUsers - 1
        for (user in firstuser..lastuser) {
            numRepeats.times {
                def count = it + 1
                assert results.contains("----- Start Test Run $user:$count:$testcaseId -----")
                assert results.contains("INFO: tc:$testcaseId user:$user iter:$count|SYS:testrun status:success")
                assert results.contains("----- End Test Run $user:$count:$testcaseId")
            }
        }
    }
    
    @Test
    void sendIncorrectTestCompletionRequest() {
        def stubRestClient = new StubFor(RESTCli)
        stubRestClient.ignore('postNoLog')
        stubRestClient.ignore('fakeget')
        stubRestClient.demand.put(5) { map ->
            return [data: [msg: 'failed', error: "cannot find ID $testrunid"]]
        }
        
        stubRestClient.use {
            def agent = new Agent(configFile)
            agent.start(bareScript)
        }
        
        // Check that we see an error
        def testlog = new File('agent.log').text
        testlog.findAll(~/cannot find ID $testrunid/).size() == 5
        testlog.findAll(~/Retry attempt \d\.\.\./).size() == 4
    }
    
    //@Test
    void retryCompletionRequestWhenFailureIsEncountered() {
        def mockRestClient = new MockFor(RESTCli)
        mockRestClient.ignore('postNoLog')
        mockRestClient.ignore('fakeget')
        mockRestClient.demand.put(5) { map ->
            def data = [:]
            data.msg = 'failed'
            data.error = "cannot find ID $testrunid"
            
            def results = [:]
            results.data = data
            return results
        }

        mockRestClient.use {
            def agent = new Agent(configFile)
            agent.start(testScript)
        }

        def foundList = new File('agent.log').text.findAll(~/INFO: cannot find ID $testrunid/)
        assert foundList.size() == 5
        
        foundList = new File('agent.log').text.findAll(~/SEVERE: cannot find ID $testrunid/)
        assert foundList.size() == 1
    }


    //TODO: Add a need for API token to the REST requests?
    //TODO: Make the logger not output to console?? Is there any performance gain?
}
